/*-------------------------------------------------------------------------
 *
 * uuid.c
 *	  Functions for the built-in type "uuid".
 *
 * Copyright (c) 2007-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/uuid.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "common/hashfn.h"
#include "lib/hyperloglog.h"
#include "libpq/pqformat.h"
#include "port/pg_bswap.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/sortsupport.h"
#include "utils/uuid.h"

 /* uuid 的排序支持 */
typedef struct
{
	int64		input_count;	/* 看到的非空值数量 */
	bool		estimating;		/* 如果估计基数为真 */

	hyperLogLogState abbr_card; /* 基数估算器 */
} uuid_sortsupport_state;

static void fc_string_to_uuid(const char *fc_source, pg_uuid_t *fc_uuid);
static int	fc_uuid_internal_cmp(const pg_uuid_t *fc_arg1, const pg_uuid_t *fc_arg2);
static int	fc_uuid_fast_cmp(Datum fc_x, Datum fc_y, SortSupport fc_ssup);
static bool fc_uuid_abbrev_abort(int fc_memtupcount, SortSupport fc_ssup);
static Datum fc_uuid_abbrev_convert(Datum fc_original, SortSupport fc_ssup);

Datum uuid_in(PG_FUNCTION_ARGS)
{
	char	   *fc_uuid_str = PG_GETARG_CSTRING(0);
	pg_uuid_t  *fc_uuid;

	fc_uuid = (pg_uuid_t *) palloc(sizeof(*fc_uuid));
	fc_string_to_uuid(fc_uuid_str, fc_uuid);
	PG_RETURN_UUID_P(fc_uuid);
}

Datum uuid_out(PG_FUNCTION_ARGS)
{
	pg_uuid_t  *fc_uuid = PG_GETARG_UUID_P(0);
	static const char fc_hex_chars[] = "0123456789abcdef";
	StringInfoData fc_buf;
	int			fc_i;

	initStringInfo(&fc_buf);
	for (fc_i = 0; fc_i < UUID_LEN; fc_i++)
	{
		int			fc_hi;
		int			fc_lo;

		 /*
		 * 我们将 uuid 值打印为一串 8、4、4、4，然后 12
		 * 个十六进制字符，每组之间用连字符
		 * ("-") 分隔。因此，在适当的位置添加连字符。
		 */
		if (fc_i == 4 || fc_i == 6 || fc_i == 8 || fc_i == 10)
			appendStringInfoChar(&fc_buf, '-');

		fc_hi = fc_uuid->data[fc_i] >> 4;
		fc_lo = fc_uuid->data[fc_i] & 0x0F;

		appendStringInfoChar(&fc_buf, fc_hex_chars[fc_hi]);
		appendStringInfoChar(&fc_buf, fc_hex_chars[fc_lo]);
	}

	PG_RETURN_CSTRING(fc_buf.data);
}

 /*
 * 我们允许 UUID 作为 32 个十六进制数字的系列，每组 4 个十六进制数字后面有一个可选的连字符，
 * 并可选地被 {} 包围。
 * (规范格式 8x-4x-4x-4x-12x，其中 "nx" 意味着 n 个十六进制
 * 数字，是用于输出的唯一格式。)
 */
static void fc_string_to_uuid(const char *fc_source, pg_uuid_t *fc_uuid)
{
	const char *fc_src = fc_source;
	bool		fc_braces = false;
	int			fc_i;

	if (fc_src[0] == '{')
	{
		fc_src++;
		fc_braces = true;
	}

	for (fc_i = 0; fc_i < UUID_LEN; fc_i++)
	{
		char		fc_str_buf[3];

		if (fc_src[0] == '\0' || fc_src[1] == '\0')
			goto syntax_error;
		memcpy(fc_str_buf, fc_src, 2);
		if (!isxdigit((unsigned char) fc_str_buf[0]) ||
			!isxdigit((unsigned char) fc_str_buf[1]))
			goto syntax_error;

		fc_str_buf[2] = '\0';
		fc_uuid->data[fc_i] = (unsigned char) strtoul(fc_str_buf, NULL, 16);
		fc_src += 2;
		if (fc_src[0] == '-' && (fc_i % 2) == 1 && fc_i < UUID_LEN - 1)
			fc_src++;
	}

	if (fc_braces)
	{
		if (*fc_src != '}')
			goto syntax_error;
		fc_src++;
	}

	if (*fc_src != '\0')
		goto syntax_error;

	return;

syntax_error:
	ereport(ERROR,
			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
			 errmsg("invalid input syntax for type %s: \"%s\"",
					"uuid", fc_source)));
}

Datum uuid_recv(PG_FUNCTION_ARGS)
{
	StringInfo	fc_buffer = (StringInfo) PG_GETARG_POINTER(0);
	pg_uuid_t  *fc_uuid;

	fc_uuid = (pg_uuid_t *) palloc(UUID_LEN);
	memcpy(fc_uuid->data, pq_getmsgbytes(fc_buffer, UUID_LEN), UUID_LEN);
	PG_RETURN_POINTER(fc_uuid);
}

Datum uuid_send(PG_FUNCTION_ARGS)
{
	pg_uuid_t  *fc_uuid = PG_GETARG_UUID_P(0);
	StringInfoData fc_buffer;

	pq_begintypsend(&fc_buffer);
	pq_sendbytes(&fc_buffer, (char *) fc_uuid->data, UUID_LEN);
	PG_RETURN_BYTEA_P(pq_endtypsend(&fc_buffer));
}

 /* 内部 uuid 比较函数 */
static int fc_uuid_internal_cmp(const pg_uuid_t *fc_arg1, const pg_uuid_t *fc_arg2)
{
	return memcmp(fc_arg1->data, fc_arg2->data, UUID_LEN);
}

Datum uuid_lt(PG_FUNCTION_ARGS)
{
	pg_uuid_t  *fc_arg1 = PG_GETARG_UUID_P(0);
	pg_uuid_t  *fc_arg2 = PG_GETARG_UUID_P(1);

	PG_RETURN_BOOL(fc_uuid_internal_cmp(fc_arg1, fc_arg2) < 0);
}

Datum uuid_le(PG_FUNCTION_ARGS)
{
	pg_uuid_t  *fc_arg1 = PG_GETARG_UUID_P(0);
	pg_uuid_t  *fc_arg2 = PG_GETARG_UUID_P(1);

	PG_RETURN_BOOL(fc_uuid_internal_cmp(fc_arg1, fc_arg2) <= 0);
}

Datum uuid_eq(PG_FUNCTION_ARGS)
{
	pg_uuid_t  *fc_arg1 = PG_GETARG_UUID_P(0);
	pg_uuid_t  *fc_arg2 = PG_GETARG_UUID_P(1);

	PG_RETURN_BOOL(fc_uuid_internal_cmp(fc_arg1, fc_arg2) == 0);
}

Datum uuid_ge(PG_FUNCTION_ARGS)
{
	pg_uuid_t  *fc_arg1 = PG_GETARG_UUID_P(0);
	pg_uuid_t  *fc_arg2 = PG_GETARG_UUID_P(1);

	PG_RETURN_BOOL(fc_uuid_internal_cmp(fc_arg1, fc_arg2) >= 0);
}

Datum uuid_gt(PG_FUNCTION_ARGS)
{
	pg_uuid_t  *fc_arg1 = PG_GETARG_UUID_P(0);
	pg_uuid_t  *fc_arg2 = PG_GETARG_UUID_P(1);

	PG_RETURN_BOOL(fc_uuid_internal_cmp(fc_arg1, fc_arg2) > 0);
}

Datum uuid_ne(PG_FUNCTION_ARGS)
{
	pg_uuid_t  *fc_arg1 = PG_GETARG_UUID_P(0);
	pg_uuid_t  *fc_arg2 = PG_GETARG_UUID_P(1);

	PG_RETURN_BOOL(fc_uuid_internal_cmp(fc_arg1, fc_arg2) != 0);
}

 /* btree 索引操作符的处理程序 */
Datum uuid_cmp(PG_FUNCTION_ARGS)
{
	pg_uuid_t  *fc_arg1 = PG_GETARG_UUID_P(0);
	pg_uuid_t  *fc_arg2 = PG_GETARG_UUID_P(1);

	PG_RETURN_INT32(fc_uuid_internal_cmp(fc_arg1, fc_arg2));
}

 /*
 * 排序支持策略例程
 */
Datum uuid_sortsupport(PG_FUNCTION_ARGS)
{
	SortSupport fc_ssup = (SortSupport) PG_GETARG_POINTER(0);

	fc_ssup->comparator = fc_uuid_fast_cmp;
	fc_ssup->ssup_extra = NULL;

	if (fc_ssup->abbreviate)
	{
		uuid_sortsupport_state *fc_uss;
		MemoryContext fc_oldcontext;

		fc_oldcontext = MemoryContextSwitchTo(fc_ssup->ssup_cxt);

		fc_uss = palloc(sizeof(uuid_sortsupport_state));
		fc_uss->input_count = 0;
		fc_uss->estimating = true;
		initHyperLogLog(&fc_uss->abbr_card, 10);

		fc_ssup->ssup_extra = fc_uss;

		fc_ssup->comparator = ssup_datum_unsigned_cmp;
		fc_ssup->abbrev_converter = fc_uuid_abbrev_convert;
		fc_ssup->abbrev_abort = fc_uuid_abbrev_abort;
		fc_ssup->abbrev_full_comparator = fc_uuid_fast_cmp;

		MemoryContextSwitchTo(fc_oldcontext);
	}

	PG_RETURN_VOID();
}

/*
 * SortSupport 比较函数
 */
static int fc_uuid_fast_cmp(Datum fc_x, Datum fc_y, SortSupport fc_ssup)
{
	pg_uuid_t  *fc_arg1 = DatumGetUUIDP(fc_x);
	pg_uuid_t  *fc_arg2 = DatumGetUUIDP(fc_y);

	return fc_uuid_internal_cmp(fc_arg1, fc_arg2);
}

 /*
 * 用于估算缩写键优化效果的回调。
 *
 * 我们不关注未缩写数据的基数，因为
 * 在权威的 uuid 比较器中没有相等快速路径。
 */
static bool fc_uuid_abbrev_abort(int fc_memtupcount, SortSupport fc_ssup)
{
	uuid_sortsupport_state *fc_uss = fc_ssup->ssup_extra;
	double		fc_abbr_card;

	if (fc_memtupcount < 10000 || fc_uss->input_count < 10000 || !fc_uss->estimating)
		return false;

	fc_abbr_card = estimateHyperLogLog(&fc_uss->abbr_card);

	 /*
	 * 如果我们有超过 10 万个不同的值，即使我们在排序许多
	 * 亿行时，我们也可能仍会收支平衡，撤销那么多
	 * 行的缩写可能不值得。在那时停止计数。
	 */
	if (fc_abbr_card > 100000.0)
	{
#ifdef TRACE_SORT
		if (trace_sort)
			elog(LOG,
				 "uuid_abbrev: estimation ends at cardinality %f"
				 " after " INT64_FORMAT " values (%d rows)",
				 fc_abbr_card, fc_uss->input_count, fc_memtupcount);
#endif
		fc_uss->estimating = false;
		return false;
	}

	 /*
	 * 目标最小基数是每 ~2k 个非空输入 1。 0.5 行
	 * 调整因子允许我们在真正病态的数据上更早中止
	 * 在前 2k 行（非空）中只有一个缩写值。
	 */
	if (fc_abbr_card < fc_uss->input_count / 2000.0 + 0.5)
	{
#ifdef TRACE_SORT
		if (trace_sort)
			elog(LOG,
				 "uuid_abbrev: aborting abbreviation at cardinality %f"
				 " below threshold %f after " INT64_FORMAT " values (%d rows)",
				 fc_abbr_card, fc_uss->input_count / 2000.0 + 0.5, fc_uss->input_count,
				 fc_memtupcount);
#endif
		return true;
	}

#ifdef TRACE_SORT
	if (trace_sort)
		elog(LOG,
			 "uuid_abbrev: cardinality %f after " INT64_FORMAT
			 " values (%d rows)", fc_abbr_card, fc_uss->input_count, fc_memtupcount);
#endif

	return false;
}

 /*
 * 用于排序支持的转换例程。将原始 uuid 表示
 * 转换为缩写键表示。我们的编码策略很简单——将
 * uuid 数据的前 `sizeof(Datum)` 字节打包到一个 Datum（在小端
 * 机器上，字节以相反的顺序存储），并将其视为
 * 无符号整数。
 */
static Datum fc_uuid_abbrev_convert(Datum fc_original, SortSupport fc_ssup)
{
	uuid_sortsupport_state *fc_uss = fc_ssup->ssup_extra;
	pg_uuid_t  *fc_authoritative = DatumGetUUIDP(fc_original);
	Datum		fc_res;

	memcpy(&fc_res, fc_authoritative->data, sizeof(Datum));
	fc_uss->input_count += 1;

	if (fc_uss->estimating)
	{
		uint32		fc_tmp;

#if SIZEOF_DATUM == 8
		fc_tmp = (uint32) fc_res ^ (uint32) ((uint64) fc_res >> 32);
#else							/* SIZEOF_DATUM != 8 */
		tmp = (uint32) res;
#endif

		addHyperLogLog(&fc_uss->abbr_card, DatumGetUInt32(hash_uint32(fc_tmp)));
	}

	 /*
	 * 在小端机器上进行字节交换。
	 *
	 * 这对于 ssup_datum_unsigned_cmp()（无符号整数
	 * 3 路比较器）在所有平台上正常工作是必要的。如果我们不这样做，
	 * 比较器将不得不调用 memcmp()，使用指向每个缩写键
	 * 第一个字节的一对指针，这样速度较慢。
	 */
	fc_res = DatumBigEndianToNative(fc_res);

	return fc_res;
}

 /* 哈希索引支持 */
Datum uuid_hash(PG_FUNCTION_ARGS)
{
	pg_uuid_t  *fc_key = PG_GETARG_UUID_P(0);

	return hash_any(fc_key->data, UUID_LEN);
}

Datum uuid_hash_extended(PG_FUNCTION_ARGS)
{
	pg_uuid_t  *fc_key = PG_GETARG_UUID_P(0);

	return hash_any_extended(fc_key->data, UUID_LEN, PG_GETARG_INT64(1));
}

Datum gen_random_uuid(PG_FUNCTION_ARGS)
{
	pg_uuid_t  *fc_uuid = palloc(UUID_LEN);

	if (!pg_strong_random(fc_uuid, UUID_LEN))
		ereport(ERROR,
				(errcode(ERRCODE_INTERNAL_ERROR),
				 errmsg("could not generate random values")));

	 /*
	 * 为 "版本 4"（伪随机）UUID 设置魔法数字，请参见
	 * http://tools.ietf.org/html/rfc4122#section-4.4
	 */
	fc_uuid->data[6] = (fc_uuid->data[6] & 0x0f) | 0x40;	 /* time_hi_and_version */
	fc_uuid->data[8] = (fc_uuid->data[8] & 0x3f) | 0x80;	 /* clock_seq_hi_and_reserved */

	PG_RETURN_UUID_P(fc_uuid);
}
